﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CERTENROLLLib;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Pkcs;
using System.Runtime.InteropServices;

namespace CertRequestor
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }

      private bool AreEqual(byte[] b1, byte[] b2)
      {
         bool bRet = false;
         if (b1 != null && b2 != null && b1.Length == b2.Length)
         {
            int i = 0;
            for (i = 0; i < b1.Length; i++)
            {
               if (b1[i] != b2[i])
                  break;
            }

            if (i == b1.Length)
               bRet = true;
         }
         return bRet;
      }

      private void generateButton_Click(object sender, EventArgs e)
      {
         CX509PrivateKey pKey = null;
         CX509CertificateRequestPkcs10 req = new CX509CertificateRequestPkcs10();
         using (new HourGlass())
         {
            if (!m_bUseCertDetails.Checked
               && (m_commonName.Text.Length == 0 || m_email.Text.Length == 0)
               )
            {
               MessageBox.Show(this, "Please fill the user details", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
               return;
            }                       

            if (m_bChooseFromStore.Checked)
            {
               X509Store store = new X509Store("MY");
               store.Open(OpenFlags.ReadOnly);
               IntPtr certCtx = Capi.CryptUIDlgSelectCertificateFromStore(store.StoreHandle,
                                    Handle,
                                    null,
                                    null,
                                    0, 0, IntPtr.Zero);
               if (certCtx == IntPtr.Zero)
               {
                  // user canceled
                  store.Close();
                  return;
               }
               else
               {
                  byte[] fingerprint = new byte[20];
                  uint fingerprintLen = 20;
                  if (Capi.CertGetCertificateContextProperty(certCtx,
                        Capi.CertPropID.CERT_HASH_PROP_ID,
                        fingerprint,
                        ref fingerprintLen))
                  {
                     bool bFound = false;
                     foreach (X509Certificate2 cert in store.Certificates)
                     {
                        byte[] certHash = cert.GetCertHash();
                        if (AreEqual(certHash, fingerprint))
                        {
                           bFound = true;

                           X509RequestInheritOptions options = X509RequestInheritOptions.InheritPrivateKey;
                           if (m_bUseCertDetails.Checked)
                              options |= X509RequestInheritOptions.InheritSubjectFlag;

                           try
                           {
                              req.InitializeFromCertificate(X509CertificateEnrollmentContext.ContextUser,
                                 cert.GetRawCertDataString(),
                                 EncodingType.XCN_CRYPT_STRING_HEX,
                                 options);
                           }
                           catch (Exception ex)
                           {
                              MessageBox.Show(this, "An exception occured while initializing request from the select certificate.\nDescription = " + ex.Message,
                                 "Error",
                                 MessageBoxButtons.OK);
                              Capi.CertFreeCertificateContext(certCtx);
                              store.Close();
                              return;
                           }

                           break;
                        }
                     }

                     if (!bFound)
                     {
                        Capi.CertFreeCertificateContext(certCtx);
                        store.Close();
                        MessageBox.Show(this, "Couldn't find the selected certificate in the store!! strange....",
                                 "Error",
                                 MessageBoxButtons.OK);
                     }
                  }
                  else
                  {
                     Capi.CertFreeCertificateContext(certCtx);
                     store.Close();
                     MessageBox.Show(this, "Failed to get the Hash of the selected certificate",
                              "Error",
                              MessageBoxButtons.OK);
                  }

                  Capi.CertFreeCertificateContext(certCtx);
                  store.Close();
               }
            }
            else
            {
               try
               {
                  pKey = new CX509PrivateKey();
                  pKey.ProviderName = ((ProviderItem)m_providers.SelectedItem).m_szName;
                  pKey.ProviderType = (X509ProviderType)((ProviderItem)m_providers.SelectedItem).m_dwType;
                  pKey.Length = (int)m_keyLength.Value;
                  pKey.KeySpec = (m_keySpec.SelectedIndex == 0) ? X509KeySpec.XCN_AT_KEYEXCHANGE : X509KeySpec.XCN_AT_SIGNATURE;
                  pKey.MachineContext = false;
                  pKey.ContainerNamePrefix = "IDRIX";

                  // check if we have to backup the private key
                  pKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_NONE;
                  if (m_pfxBackup.Checked)
                     pKey.ExportPolicy |= X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_ARCHIVING_FLAG | X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_ARCHIVING_FLAG;
                  if (m_keyExportable.Checked)
                     pKey.ExportPolicy |= X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG | X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;

                  pKey.Create();                     
               }
               catch (Exception ex)
               {
                  MessageBox.Show(this, "An exception occured while generating the private key.\nDescription = " + ex.ToString(),
                     "Error",
                     MessageBoxButtons.OK);
                  pKey = null;
                  return;
               }

               try
               {
                  req.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser,
                     pKey,
                     "");
               }
               catch (Exception ex)
               {
                  MessageBox.Show(this, "An exception occured while initializing request from generated private key.\nDescription = " + ex.ToString(),
                     "Error",
                     MessageBoxButtons.OK);
                  pKey.Delete();
                  return;
               }
            }
            req.SmimeCapabilities = true;

            if (!m_bChooseFromStore.Checked || !m_bUseCertDetails.Checked)
            {
               string DN = "CN=" + m_commonName.Text + ", E=" + m_email.Text;
               if (m_organization.Text.Length > 0)
                  DN += ", O=" + m_organization.Text;
               req.Subject = new CX500DistinguishedName();
               req.Subject.Encode(DN, X500NameFlags.XCN_CERT_X500_NAME_STR);
            }

            try
            {
               req.Encode();
            }
            catch (Exception ex)
            {
               MessageBox.Show(this, "An exception occured while creating the certificate request.\nDescription = " + ex.ToString(),
                  "Error",
                  MessageBoxButtons.OK);
               if (pKey != null)
                  pKey.Delete();
               return;
            }

            string reqBase64 = req.get_RawData(EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER);
            bool bSaved = false;
            if (m_saveDialog.ShowDialog() == DialogResult.OK)
            {
               System.IO.Stream st = m_saveDialog.OpenFile();
               if (st != null)
               {
                  ASCIIEncoding enc = new ASCIIEncoding();
                  byte[] reqBytes = enc.GetBytes(reqBase64);
                  st.Write(reqBytes, 0, reqBytes.Length);
                  st.Flush();
                  st.Close();
                  bSaved = true;
               }
               else
                  MessageBox.Show("Failed to open file for writing", "I/O Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            if (!bSaved)
            {
               if (pKey != null)
               {
                  pKey.Delete();
                  pKey = null;
               }
            }
         }
         if (pKey != null)
         {
            if (m_pfxBackup.Checked)
            {
               if (req.IsSmartCard())
               {
                  MessageBox.Show("Certificate request saved on file.\nPrivate key can't be backuped because it was created in a smart card", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
               }
               else
               {
                  Form2 dlg = new Form2();
                  DialogResult ret = dlg.ShowDialog(this);
                  if (ret == DialogResult.OK)
                  {
                     bool bStatus = ExportPfx(pKey.ProviderName,
                        (Capi.ProviderType) pKey.ProviderType,
                        pKey.ContainerName,
                        pKey.KeySpec,
                        dlg.GetPfxPath(),
                        dlg.GetPassword());
                     if (bStatus)
                        MessageBox.Show("Certificate Request saved on file.\nPrivate key has beed backuped successfully.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
                     else
                        MessageBox.Show("Certificate Request saved on file.\nPrivate key backup was canceled because of an error", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);

                  }
                  else
                     MessageBox.Show("Certificate Request saved on file.\nPrivate key backup was canceled", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
                  dlg.Dispose();
               }               
            }
            else
               MessageBox.Show("Certificate Request saved on file", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);            
            pKey.Close();
         }
      }

      private bool ExportPfx(string szProviderName,
                             Capi.ProviderType provType,
                             string szContainer,
                             X509KeySpec keySpec,
                             string szPfxPath,
                             string szPassword)
      {
         Capi.CRYPTOAPI_BLOB subject = new Capi.CRYPTOAPI_BLOB();
         string DN = "CN=" + m_commonName.Text + ", E=" + m_email.Text;
         if (m_organization.Text.Length > 0)
            DN += ", O=" + m_organization.Text;

         int uLen = 0;
         bool bStatus = Capi.CertStrToName(Capi.CertEncodingType.X509_ASN_ENCODING,
            DN,
            Capi.CertNameFlags.CERT_X500_NAME_STR,
            IntPtr.Zero,
            null,
            ref uLen,
            IntPtr.Zero);
         if (bStatus)
         {
            byte[] pbSubject = new byte[uLen];
            bStatus = Capi.CertStrToName(Capi.CertEncodingType.X509_ASN_ENCODING,
               DN,
               Capi.CertNameFlags.CERT_X500_NAME_STR,
               IntPtr.Zero,
               pbSubject,
               ref uLen,
               IntPtr.Zero);
            if (bStatus)
            {
               subject.cbData = uLen;
               subject.pbData = Marshal.AllocHGlobal((int)uLen);
               Marshal.Copy(pbSubject, 0, subject.pbData, (int)uLen);

               Capi.CRYPT_KEY_PROV_INFO keyInfo = new Capi.CRYPT_KEY_PROV_INFO();
               keyInfo.pwszProvName = szProviderName;
               keyInfo.dwFlags = (int)0;
               keyInfo.dwProvType = (UInt32)provType;
               keyInfo.pwszContainerName = szContainer;
               keyInfo.dwKeySpec = (int)Capi.KeySpecType.AT_KEYEXCHANGE;
               keyInfo.cProvParam = 0;
               keyInfo.rgProvParam = IntPtr.Zero;

               IntPtr pCertCtx = Capi.CertCreateSelfSignCertificate(IntPtr.Zero,
                  ref subject,
                  0,
                  ref keyInfo,
                  IntPtr.Zero,
                  IntPtr.Zero,
                  IntPtr.Zero,
                  IntPtr.Zero);
               if (pCertCtx != IntPtr.Zero)
               {
                  IntPtr hStore = Capi.CertOpenStore(Capi.CertStoreType.CERT_STORE_PROV_MEMORY,
                     Capi.CertEncodingType.X509_ASN_ENCODING | Capi.CertEncodingType.PKCS_7_ASN_ENCODING,
                     IntPtr.Zero, 0, IntPtr.Zero);
                  if (hStore != IntPtr.Zero)
                  {
                     bStatus = Capi.CertAddCertificateContextToStore(hStore, pCertCtx, Capi.CertAddDisposition.CERT_STORE_ADD_ALWAYS, IntPtr.Zero);
                     if (bStatus)
                     {
                        Capi.CRYPTOAPI_BLOB pfxBlob = new Capi.CRYPTOAPI_BLOB();
                        bStatus = Capi.PFXExportCertStoreEx(hStore, ref pfxBlob, szPassword, IntPtr.Zero, Capi.PfxExportFlags.EXPORT_PRIVATE_KEYS | Capi.PfxExportFlags.REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY);
                        if (bStatus)
                        {
                           pfxBlob.pbData = Marshal.AllocHGlobal(pfxBlob.cbData);
                           bStatus = Capi.PFXExportCertStoreEx(hStore, ref pfxBlob, szPassword, IntPtr.Zero, Capi.PfxExportFlags.EXPORT_PRIVATE_KEYS | Capi.PfxExportFlags.REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY);
                           if (bStatus)
                           {
                              try
                              {
                                 byte[] pbData = new byte[pfxBlob.cbData];
                                 Marshal.Copy(pfxBlob.pbData, pbData, 0, pfxBlob.cbData);
                                 System.IO.FileStream f = new System.IO.FileStream(szPfxPath, System.IO.FileMode.Create);
                                 f.Write(pbData, 0, pbData.Length);
                                 f.Flush();
                                 f.Close();
                              }
                              catch (Exception )
                              {
                                 bStatus = false;
                              }

                           }
                           else
                           {
                              int lastError = Marshal.GetLastWin32Error();
                              bStatus = false;
                           }
                        }
                        else
                        {
                           int lastError = Marshal.GetLastWin32Error();
                           bStatus = false;
                        }
                     }
                     Capi.CertCloseStore(hStore, 0);
                  }
                  else
                  {
                     int lastError = Marshal.GetLastWin32Error();
                     bStatus = false;
                  }
                  Capi.CertFreeCertificateContext(pCertCtx);
               }
               else
               {
                  int lastError = Marshal.GetLastWin32Error();
                  bStatus = false;
               }
            }
         }

         return bStatus;
      }

      private void Form1_Load(object sender, EventArgs e)
      {
         // Get CSPs list
         StringBuilder szName = new StringBuilder(1024);
         UInt32 dwIndex = 0, dwType = 0;
         UInt32 dwLen = 1024;
         while (Capi.CryptEnumProviders(
                 dwIndex,
                 IntPtr.Zero,
                 0,
                 ref dwType,
                 szName,
                 ref dwLen))
         {
            m_providers.Items.Add(new ProviderItem(szName, dwType));
            dwIndex++;
            dwLen = 1024;
         }
         m_providers.SelectedIndex = 0;
         m_keySpec.SelectedIndex = 0;
      }

      private void m_bChooseFromStore_CheckedChanged(object sender, EventArgs e)
      {
         if (m_bChooseFromStore.Checked)
         {
            m_providers.Enabled = false;
            m_keyLength.Enabled = false;
            m_keySpec.Enabled = false;
            m_bUseCertDetails.Enabled = true;
            m_pfxBackup.Enabled = false;
            m_pfxBackup.Checked = false;
            m_keyExportable.Enabled = false;
            m_keyExportable.Checked = false;
         }
         else
         {
            m_providers.Enabled = true;
            m_keyLength.Enabled = true;
            m_keySpec.Enabled = true;
            m_bUseCertDetails.Checked = false;
            m_bUseCertDetails.Enabled = false;
            m_pfxBackup.Enabled = true;
            m_keyExportable.Enabled = true;
         }
      }

      private void m_bUseCertDetails_CheckedChanged(object sender, EventArgs e)
      {
         if (m_bUseCertDetails.Checked)
         {
            m_commonName.Enabled = false;
            m_email.Enabled = false;
            m_organization.Enabled = false;
         }
         else
         {
            m_commonName.Enabled = true;
            m_email.Enabled = true;
            m_organization.Enabled = true;
         }
      }

      private void m_pfxBackup_CheckedChanged(object sender, EventArgs e)
      {
         if (m_pfxBackup.Checked)
            m_keyExportable.Checked = true;
      }

      private void m_keyExportable_CheckedChanged(object sender, EventArgs e)
      {
         if (!m_keyExportable.Checked)
            m_pfxBackup.Checked = false;
      }
   }

   public class ProviderItem
   {
      public ProviderItem(StringBuilder szName, UInt32 dwType)
      {
         m_dwType = dwType;
         m_szName = szName.ToString();
      }

      public override string ToString()
      {
         return m_szName;
      }

      public UInt32 m_dwType;
      public string m_szName;
   }

   /*
    * Used for busy notification
    */

   public class HourGlass : IDisposable
   {
      public HourGlass()
      {
         Enabled = true;
      }
      public void Dispose()
      {
         Enabled = false;
      }
      public static bool Enabled
      {
         get { return Application.UseWaitCursor; }
         set
         {
            if (value == Application.UseWaitCursor) return;
            Application.UseWaitCursor = value;
            Form f = Form.ActiveForm;
            if (f != null && f.Handle != null)   // Send WM_SETCURSOR
               SendMessage(f.Handle, 0x20, f.Handle, (IntPtr)1);
         }
      }
      [DllImport("user32.dll")]
      private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
   }

}
